package com.opcode.nebulamobil.base.services;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;

import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaPlayer;
import android.media.MediaPlayer.OnCompletionListener;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.os.Build;
import android.os.Environment;
import android.os.IBinder;
import android.util.Log;

import com.opcode.nebulamobil.base.interfaces.NebulaStatusListener;
import com.opcode.nebulamobil.base.interfaces.PushTalkDataListener;
import com.opcode.nebulamobil.base.interfaces.TryConnectionCommandListener;
import com.opcode.nebulamobil.information.ApplicationInfo;
import com.opcode.nebulamobil.information.dataforms.costadataforms.SettingsDataForm;
import com.opcode.nebulamobil.information.requests.costarequests.PushTalkActivationReq;
import com.opcode.nebulamobil.information.responses.JSONResponse;
import com.opcode.nebulamobil.information.responses.costaresponses.PushTalkActivationResp;
import com.opcode.nebulamobil.information.responses.costaresponses.PushTalkResp;
import com.opcode.nebulamobil.utility.JsonConverter;
import com.opcode.nebulamobil.utility.STSLog;
import com.opcode.nebulamobil.utility.UniqueIdCreator;

/**
 * Bu sınıf,bas-konuş sisteminin alt yapısını oluşturmaktadır.
 * Bu servis sayesinde mobil uygulamadan elde edilen ses datası sunucuya iletilir.
 * @author Okan ERDOĞAN
 *
 * Oct 11, 2014
 */
public class PushTalkService extends Service implements PushTalkDataListener,TryConnectionCommandListener,NebulaStatusListener{

	
	private MediaPlayer m;
	private String tempFileName,mp3FileName;
	private String fileName;
    private FileOutputStream os = null;
    private boolean isFileObjectsCreated=false;
	private SharedPreferences aData = null;
	private SettingsDataForm settingsInfo;
	private  byte[] sendData;
    private  byte[] receiveData;
    private InetAddress iPAddress;
    private DatagramSocket clientSocket,serverSocket;
    private DatagramPacket sendPacket;
    
    private Thread sendThread,readThread;
    
    private static final int RECORDER_BPP = 16;
    private static final String AUDIO_RECORDER_FILE_EXT_WAV = ".mp3";
    private static final String AUDIO_RECORDER_FOLDER = "AudioRecorder";
    private static final String AUDIO_RECORDER_TEMP_FILE = "record_temp.raw";
    private static final int RECORDER_SAMPLERATE = 8000;
    private static final int RECORDER_CHANNELS = AudioFormat.CHANNEL_IN_STEREO;
    private static final int RECORDER_AUDIO_ENCODING = AudioFormat.ENCODING_PCM_16BIT;
    private int bufferSize = 0;
    
    private static int nebulaPushTalkPort=0;

	
	public void onCreate(){
		super.onCreate();
		
		
		 this.m = new MediaPlayer();
		 m.setOnCompletionListener(new OnCompletionListener() {

		        @Override
		        public void onCompletion(MediaPlayer mp) {
		            mp.reset();
		            System.out.println("Mediaplayer'a reset atıldı.");
		            File file=new File(mp3FileName);
		            if(file.exists()){
		            	file.delete();
		            }
		        }

		    });
		bufferSize = AudioRecord.getMinBufferSize(RECORDER_SAMPLERATE,RECORDER_CHANNELS,RECORDER_AUDIO_ENCODING);

		/*
		 * Service oluşturulurken gerekli nesneler ve ayarlamalar yapılıyor.
		 * Bu service öncelikle STS uygulaması içerisinden gönderilecek olan bas-konuş datalarının dinleyicisi olarak kendisini ayarlıyor.
		 */
		ApplicationInfo.setPushTalkDataListener(this);

		if(bufferSize<=0){
			this.sendData= new byte[30000];
		}else{
			this.sendData = new byte[bufferSize]; 	
		}
		this.receiveData = new byte[30000];
		
		
		this.sendThread=new Thread(new Runnable(){

			@Override
			public void run() {
				while(true){
					System.out.println("PushTalk Döngu-1");
					synchronized (sendThread) {
						if(sendPacket!=null){
							if(clientSocket!=null){
								try {
									clientSocket.send(sendPacket);
								} catch (IOException e) {
									e.printStackTrace();
								}	
							}
						}
						
						try {
							sendThread.wait();
							
						} catch (InterruptedException e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}	
					}
					
				}
			}
			
		});	
		
		this.readThread=new Thread(new Runnable(){

			@Override
			public void run() {
				
			while(true) { 
				System.out.println("PushTalk Döngu-2");

					try {
						if(serverSocket!=null){
							DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length); 

							serverSocket.receive(receivePacket);
							String sentence = new String(receivePacket.getData(),0,receivePacket.getLength()); 
								
							InetAddress IPAddress = receivePacket.getAddress(); 
							int port = receivePacket.getPort();
								
							System.out.println("Push Talk Service:"+sentence);

							writeAudioDataToFile(sentence);
							
						}else{
							System.out.println("ServerSocket null oldu.");
						}
					} catch (IOException e) {
						e.printStackTrace();
					} 
					
					} 
			}
		});	
		
		

	}
	
	
	/**
	 * 
	 */
	public int onStartCommand(Intent intent,int flags,int startId){
		super.onStartCommand(intent, flags, startId);	
		
		try {
			System.out.println("PushTalk socket oluşturuluyor.");
			serverSocket = new DatagramSocket(1798);
			
			clientSocket = new DatagramSocket();
			
			ApplicationInfo.setNebulaStatusListener(this);

		} catch (SocketException e1) {
			e1.printStackTrace();
		}
  
		this.readThread.start();

		return START_NOT_STICKY;
	}
	
	
	private void writeAudioDataToFile(String response){
		 
		 
		 JSONResponse responseObject=(JSONResponse) JsonConverter.fromJsonString(response, JSONResponse.class);
			
			if(responseObject.getType().equals("PushTalkResp")){
				
				PushTalkResp pushTalkResp=(PushTalkResp) JsonConverter.fromJsonString(response, PushTalkResp.class);

				if(!isFileObjectsCreated){
					createFileObjects();	
				}
		                
		        if(null != os){
		        	
		        	if(pushTalkResp.isStopTalking()){
		        		 copyWaveFile(tempFileName,getFilename());
		        		 deleteTempFile(os,tempFileName);
		        		 File file = new File(mp3FileName);
		        		
		        		 if(file.exists()){		        			
		        			 System.out.println(mp3FileName+" bulundu.");
				             playSound(mp3FileName); 
		        		 }else{
		        			 System.out.println(mp3FileName+" bulunamadı.");
		        		 }

						
					}else{	  
						try {
							os.write(pushTalkResp.getVoiceData(), 0, pushTalkResp.getVoiceData().length);
						} catch (IOException e) {
							e.printStackTrace();
						}		  
		                
					}
		        }
			}else if(responseObject.getType().equals(PushTalkActivationResp.class.getSimpleName())){
				PushTalkActivationResp pushTalkActivationResp=(PushTalkActivationResp) JsonConverter.fromJsonString(response, PushTalkActivationResp.class);
				nebulaPushTalkPort=pushTalkActivationResp.getPort();
				System.out.println("NebulaPushTalkPort:"+nebulaPushTalkPort);
			}
		}
	
	
	 public void createFileObjects(){
		 	String filepath = Environment.getExternalStorageDirectory().getPath();
	        File file = new File(filepath,AUDIO_RECORDER_FOLDER);
	        
	        if(!file.exists()){
	                file.mkdirs();
	        }
	        
	        File tempFile = new File(filepath,AUDIO_RECORDER_TEMP_FILE);
	        
	        if(tempFile.exists())
	                tempFile.delete();
	         
	        	//Bu bölümde temp dosyasına yazmada kullanılacak olan dosya adı elde ediliyor.
		       //String filename = getTempFilename();
		       this.os = null;
		       tempFileName=file.getAbsolutePath() + "/" + AUDIO_RECORDER_TEMP_FILE;
				
		        try {
		        	os = new FileOutputStream(tempFileName);
		        } catch (FileNotFoundException e) {
		        	e.printStackTrace();
		        }
			  isFileObjectsCreated=true;
		}
	 
	 
	 /**
		 * 
		 */
		private void deleteTempFile(FileOutputStream os,String fileName) {
	        File file = new File(fileName);
	        
	        file.delete();
			isFileObjectsCreated=false;

	        try {
 			os.close();
     	} catch (IOException e) {
     		e.printStackTrace();
     	}
		}

		
		
		/**
		 * Kayıt işlemi sonucunda elde edilen temp dosyasından ses dosyası elde etmek için gerekli dosya adı bu metod ile elde edilir.
		 * @return
		 */
		private String getFilename(){
	        String filepath = Environment.getExternalStorageDirectory().getPath();
	        File file = new File(filepath,AUDIO_RECORDER_FOLDER);
	        
	        if(!file.exists()){
	                file.mkdirs();
	        } 
	        this.mp3FileName=file.getAbsolutePath() + "/" + System.currentTimeMillis() + AUDIO_RECORDER_FILE_EXT_WAV;
	        return this.mp3FileName;
		}
	
		
		
		
		
		
		
		/**
		 * 
		 * @param inFilename
		 * @param outFilename
		 */
		private void copyWaveFile(String inFilename,String outFilename){
	        FileInputStream in = null;
	        FileOutputStream out = null;
	        long totalAudioLen = 0;
	        long totalDataLen = totalAudioLen + 36;
	        long longSampleRate = RECORDER_SAMPLERATE;
	        int channels = 2;
	        long byteRate = RECORDER_BPP * RECORDER_SAMPLERATE * channels/8;
	        
	        byte[] data = new byte[bufferSize];
	        
	        try {
	                in = new FileInputStream(inFilename);
	                out = new FileOutputStream(outFilename);
	                totalAudioLen = in.getChannel().size();
	                totalDataLen = totalAudioLen + 36;
	                
	                Log.v("PushTalkService", "File size: " + totalDataLen);
	                Log.v("PushTalkService", "In File Name: " + inFilename);
	                Log.v("PushTalkService", "Out File Name: " + outFilename);

	                
	                WriteWaveFileHeader(out, totalAudioLen, totalDataLen, longSampleRate, channels, byteRate);
	                
	                while(in.read(data) != -1){
	                 out.write(data);
	                }
	                
	                in.close();
	                out.close();
	                
	                
	        } catch (FileNotFoundException e) {
	                e.printStackTrace();
	        } catch (IOException e) {
	                e.printStackTrace();
	        }
	}
		
		
		
		/**
		 * 
		 * @param out
		 * @param totalAudioLen
		 * @param totalDataLen
		 * @param longSampleRate
		 * @param channels
		 * @param byteRate
		 * @throws IOException
		 */
		private void WriteWaveFileHeader(FileOutputStream out, long totalAudioLen,long totalDataLen, long longSampleRate, int channels,long byteRate) throws IOException {
	        
	        byte[] header = new byte[44];
	        
	        header[0] = 'R';  // RIFF/WAVE header
	        header[1] = 'I';
	        header[2] = 'F';
	        header[3] = 'F';
	        header[4] = (byte) (totalDataLen & 0xff);
	        header[5] = (byte) ((totalDataLen >> 8) & 0xff);
	        header[6] = (byte) ((totalDataLen >> 16) & 0xff);
	        header[7] = (byte) ((totalDataLen >> 24) & 0xff);
	        header[8] = 'W';
	        header[9] = 'A';
	        header[10] = 'V';
	        header[11] = 'E';
	        header[12] = 'f';  // 'fmt ' chunk
	        header[13] = 'm';
	        header[14] = 't';
	        header[15] = ' ';
	        header[16] = 16;  // 4 bytes: size of 'fmt ' chunk
	        header[17] = 0;
	        header[18] = 0;
	        header[19] = 0;
	        header[20] = 1;  // format = 1
	        header[21] = 0;
	        header[22] = (byte) channels;
	        header[23] = 0;
	        header[24] = (byte) (longSampleRate & 0xff);
	        header[25] = (byte) ((longSampleRate >> 8) & 0xff);
	        header[26] = (byte) ((longSampleRate >> 16) & 0xff);
	        header[27] = (byte) ((longSampleRate >> 24) & 0xff);
	        header[28] = (byte) (byteRate & 0xff);
	        header[29] = (byte) ((byteRate >> 8) & 0xff);
	        header[30] = (byte) ((byteRate >> 16) & 0xff);
	        header[31] = (byte) ((byteRate >> 24) & 0xff);
	        header[32] = (byte) (2 * 16 / 8);  // block align
	        header[33] = 0;
	        header[34] = RECORDER_BPP;  // bits per sample
	        header[35] = 0;
	        header[36] = 'd';
	        header[37] = 'a';
	        header[38] = 't';
	        header[39] = 'a';
	        header[40] = (byte) (totalAudioLen & 0xff);
	        header[41] = (byte) ((totalAudioLen >> 8) & 0xff);
	        header[42] = (byte) ((totalAudioLen >> 16) & 0xff);
	        header[43] = (byte) ((totalAudioLen >> 24) & 0xff);

	        out.write(header, 0, 44);
		}
		
		
		public void playSound(String soundPath){
		   
			
			
		    try {
		    	if(!m.isPlaying()){
		    		 m.setDataSource(soundPath);            
				     m.prepare();
				     m.setVolume(100f, 100f);
				     m.setLooping(false);
				     m.start();
		    	}
		    } catch (Exception e) {
				STSLog.writeLog(e,this.getClass().getCanonicalName(),"Ses dosyası oynatılırken hata alındı.");														
		    }
		 }
		
		
	
	/**
	 * Bir sharedPreferences nesnesi elde etmek için kullanılan metoddur.
	 * Bu metod neticesinde elde edilen aData nesnesi ile uygulamada bazı veriler sharedPreferences içine kayıt edilebilir.
	 * Bu projede sharedPreferences'a kayıt edilen verilen varsayılan olarak başka uygulamaların erişimine kapatılmıştır.
	 * Ancak istenirse bu veriler herkese açık hale getirilebilir.
	 * @param fileName
	 * @param context
	 */
	public void createSharedPreferencesManager(String fileName,Context context){
		this.fileName=fileName;
		this.aData= context.getSharedPreferences(fileName, Context.MODE_PRIVATE);
	}
	
	
	/**
	 * Parametre olarak verilen key ve value verilerine göre sharedPreferences'a veri kaydetmek için kullanılır.
	 * @param key
	 * @param value
	 */
	public void writeToSharedPreferences(String key,boolean value){
		SharedPreferences.Editor prefEditor = aData.edit();
        prefEditor.putBoolean(key, value);
        prefEditor.commit();
	}
	
	
	/**
	 * SharedPreferences'a bir key ile kayıt edilen verileri yine aynı key ile elde etmeyi sağlar.
	 * Bu metodun geri dönüş tipi boolean'dır.
	 * @param key
	 * @param defValue
	 * @return
	 */
	public boolean readFromSharedPreferences(String key,boolean defValue){
		boolean value=aData.getBoolean(key, defValue);
		return value;
	}
	
	
	/**
	 * String tipindeki verilerin sharedPreferences'a kayıt edilmesini sağlayan metoddur.
	 * @param key
	 * @param value
	 */
	public void writeToSharedPreferences(String key,String value){
		SharedPreferences.Editor prefEditor = aData.edit();
        prefEditor.putString(key, value);
        prefEditor.commit();
	}
	
	
	/**
	 * String tipindeki verilerin sharedPreferences'tan elde edilmesini sağlayan metoddur.
	 * @param key
	 * @param defValue
	 * @return
	 */
	public String readFromSharedPreferences(String key,String defValue){
		String value=aData.getString(key, defValue);
		return value;
	}
	
	
	
	@Override
	public void tryConnectionAgain() {
		// TODO Auto-generated method stub
		
	}

	@Override
	public IBinder onBind(Intent intent) {
		return null;
	}


	@Override
	public void pushMessageCame(String pushMessage) {
				
		if(settingsInfo==null){
			/*
			 * Daha önce sharedpreferences'a kayıt edilen settingsInfo bilgileri elde ediliyor.
			 * Bu bilgiler içinde bağlanılmak istenen sunucunun ip ve port değerleri bulunuyor.
			 */
			createSharedPreferencesManager("sharedpreferences",getApplication());
			String settingsDataFormAsJson=readFromSharedPreferences("settingsInfo", null);	
			settingsInfo=(SettingsDataForm) JsonConverter.fromJsonString(settingsDataFormAsJson, SettingsDataForm.class);
		}
		
		if(settingsInfo!=null){
			if(nebulaPushTalkPort!=0){
				try {
					this.iPAddress = InetAddress.getByName(settingsInfo.getServerIp());
					sendData=pushMessage.getBytes();
					this.sendPacket =new DatagramPacket(sendData, sendData.length, iPAddress, nebulaPushTalkPort); 
					this.sendPushTalkMessage();
				}catch (UnknownHostException e) {
					e.printStackTrace();
				}
			}
		}
	}
	
	/**
	 * PushTalkService ilk çalıştığında aktivasyon için bu request gönderilir.
	 */
	private void sendPushTalkActivationRequest(){
		
		WifiManager manager = (WifiManager) this.getSystemService(this.WIFI_SERVICE);
		WifiInfo info = manager.getConnectionInfo();
		String macAddress = info.getMacAddress();
		String versionName="0.0";
		int versionCode=0;
		PackageInfo pInfo;
		String brand = Build.BRAND;
		String model = Build.MODEL;
		try {
			pInfo = getPackageManager().getPackageInfo(getPackageName(), 0);
			versionName = pInfo.versionName;
			versionCode=pInfo.versionCode;

		} catch (NameNotFoundException e) {
			STSLog.writeLog(e,this.getClass().getCanonicalName(),"Paket ismi bulunamadı.");														
		}
		
		PushTalkActivationReq pushTalkActivationReq=new PushTalkActivationReq();
		pushTalkActivationReq.setMacAddress(macAddress);
		pushTalkActivationReq.setVersionCode(versionCode);
		pushTalkActivationReq.setVersionName(versionName);
		pushTalkActivationReq.setDeviceType(brand+" "+model);
		
		if(ApplicationInfo.isTablet()){
			pushTalkActivationReq.setAppType("CostaKitchenTerminal");
		}else{
			pushTalkActivationReq.setAppType("CostaHandTerminal");
		}
		pushTalkActivationReq.setType("PushTalkActivationReq");
		pushTalkActivationReq.setRequestId(UniqueIdCreator.createUniqueID());
		
		
		if(settingsInfo==null){
			/*
			 * Daha önce sharedpreferences'a kayıt edilen settingsInfo bilgileri elde ediliyor.
			 * Bu bilgiler içinde bağlanılmak istenen sunucunun ip ve port değerleri bulunuyor.
			 */
			createSharedPreferencesManager("sharedpreferences",getApplication());
			String settingsDataFormAsJson=readFromSharedPreferences("settingsInfo", null);	
			settingsInfo=(SettingsDataForm) JsonConverter.fromJsonString(settingsDataFormAsJson, SettingsDataForm.class);
		}
		
		if(settingsInfo!=null){
			try {
				this.clientSocket = new DatagramSocket();
				this.iPAddress = InetAddress.getByName(settingsInfo.getServerIp());
			
				sendData=JsonConverter.toJsonString(pushTalkActivationReq).getBytes();
				System.out.println(JsonConverter.toJsonString(pushTalkActivationReq));

				this.sendPacket =new DatagramPacket(sendData, sendData.length, iPAddress, 2000); 
				this.sendPushTalkMessage();
			} catch (SocketException e) {
				e.printStackTrace();
			} catch (UnknownHostException e) {
				e.printStackTrace();
			}
		}
	
	}
	
	public void onDestroy(){
		super.onDestroy();
		System.out.println("PushTalkService kapandı.");
		serverSocket.close();
		clientSocket.close();
	}
	
	public void sendPushTalkMessage(){
		
		if(this.sendThread!=null){
			if (sendThread.getState() == Thread.State.NEW ){
				this.sendThread.start();
				Log.i("PushTalkService", "Thread başlatıldı.");	
			}else{
				synchronized (sendThread) {
					sendThread.notifyAll();
					Log.i("PushTalkService", "sendThread hali hazırda çalışıyor.");	
				}
					
			}
			
		}

	}


	@Override
	public void nebulaReadyStatus(boolean isNebulaReady) {
		if(isNebulaReady){
			// Aktivasyon için gerekli requesti hazırlayarak Nebula'ya gönderir.
			sendPushTalkActivationRequest();
		}
	}

}
